 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.dialogs;

 import java.io.IOException ;
 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.ArrayList ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.jface.dialogs.TrayDialog;
 import org.eclipse.jface.resource.JFaceColors;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StyleRange;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.MouseAdapter;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseMoveListener;
 import org.eclipse.swt.events.TraverseEvent;
 import org.eclipse.swt.events.TraverseListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Cursor;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.browser.IWebBrowser;
 import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
 import org.eclipse.ui.internal.WorkbenchMessages;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.about.AboutItem;
 import org.eclipse.ui.internal.misc.StatusUtil;
 import org.eclipse.ui.statushandlers.StatusManager;

 /**
  * Abstract superclass of about dialogs
  */

 public abstract class ProductInfoDialog extends TrayDialog {

     private AboutItem item;

     private Cursor handCursor;

     private Cursor busyCursor;

     private boolean mouseDown = false;

     private boolean dragEvent = false;

     /**
      * Create an instance of this Dialog
      */
     public ProductInfoDialog(Shell parentShell) {
         super(parentShell);
     }

     /**
      * Adds listeners to the given styled text
      */
     protected void addListeners(StyledText styledText) {
         styledText.addMouseListener(new MouseAdapter() {
             public void mouseDown(MouseEvent e) {
                 if (e.button != 1) {
                     return;
                 }
                 mouseDown = true;
             }

             public void mouseUp(MouseEvent e) {
                 mouseDown = false;
                 StyledText text = (StyledText) e.widget;
                 int offset = text.getCaretOffset();
                 if (dragEvent) {
                     // don't activate a link during a drag/mouse up operation
 dragEvent = false;
                     if (item != null && item.isLinkAt(offset)) {
                         text.setCursor(handCursor);
                     }
                 } else if (item != null && item.isLinkAt(offset)) {
                     text.setCursor(busyCursor);
                     openLink(item.getLinkAt(offset));
                     StyleRange selectionRange = getCurrentRange(text);
                     text.setSelectionRange(selectionRange.start,
                             selectionRange.length);
                     text.setCursor(null);
                 }
             }
         });

         styledText.addMouseMoveListener(new MouseMoveListener() {
             public void mouseMove(MouseEvent e) {
                 // Do not change cursor on drag events
 if (mouseDown) {
                     if (!dragEvent) {
                         StyledText text = (StyledText) e.widget;
                         text.setCursor(null);
                     }
                     dragEvent = true;
                     return;
                 }
                 StyledText text = (StyledText) e.widget;
                 int offset = -1;
                 try {
                     offset = text.getOffsetAtLocation(new Point(e.x, e.y));
                 } catch (IllegalArgumentException ex) {
                     // leave value as -1
 }
                 if (offset == -1) {
                     text.setCursor(null);
                 } else if (item != null && item.isLinkAt(offset)) {
                     text.setCursor(handCursor);
                 } else {
                     text.setCursor(null);
                 }
             }
         });

         styledText.addTraverseListener(new TraverseListener() {
             public void keyTraversed(TraverseEvent e) {
                 StyledText text = (StyledText) e.widget;
                 switch (e.detail) {
                 case SWT.TRAVERSE_ESCAPE:
                     e.doit = true;
                     break;
                 case SWT.TRAVERSE_TAB_NEXT:
                     //Previously traverse out in the backward direction?
 Point nextSelection = text.getSelection();
                     int charCount = text.getCharCount();
                     if ((nextSelection.x == charCount)
                             && (nextSelection.y == charCount)) {
                         text.setSelection(0);
                     }
                     StyleRange nextRange = findNextRange(text);
                     if (nextRange == null) {
                         // Next time in start at beginning, also used by
 // TRAVERSE_TAB_PREVIOUS to indicate we traversed out
 // in the forward direction
 text.setSelection(0);
                         e.doit = true;
                     } else {
                         text.setSelectionRange(nextRange.start,
                                 nextRange.length);
                         e.doit = true;
                         e.detail = SWT.TRAVERSE_NONE;
                     }
                     break;
                 case SWT.TRAVERSE_TAB_PREVIOUS:
                     //Previously traverse out in the forward direction?
 Point previousSelection = text.getSelection();
                     if ((previousSelection.x == 0)
                             && (previousSelection.y == 0)) {
                         text.setSelection(text.getCharCount());
                     }
                     StyleRange previousRange = findPreviousRange(text);
                     if (previousRange == null) {
                         // Next time in start at the end, also used by
 // TRAVERSE_TAB_NEXT to indicate we traversed out
 // in the backward direction
 text.setSelection(text.getCharCount());
                         e.doit = true;
                     } else {
                         text.setSelectionRange(previousRange.start,
                                 previousRange.length);
                         e.doit = true;
                         e.detail = SWT.TRAVERSE_NONE;
                     }
                     break;
                 default:
                     break;
                 }
             }
         });

         //Listen for Tab and Space to allow keyboard navigation
 styledText.addKeyListener(new KeyAdapter() {
             public void keyPressed(KeyEvent event) {
                 StyledText text = (StyledText) event.widget;
                 if (event.character == ' ' || event.character == SWT.CR) {
                     if (item != null) {
                         //Be sure we are in the selection
 int offset = text.getSelection().x + 1;

                         if (item.isLinkAt(offset)) {
                             text.setCursor(busyCursor);
                             openLink(item.getLinkAt(offset));
                             StyleRange selectionRange = getCurrentRange(text);
                             text.setSelectionRange(selectionRange.start,
                                     selectionRange.length);
                             text.setCursor(null);
                         }
                     }
                     return;
                 }
             }
         });
     }

     /**
      * Gets the busy cursor.
      * @return the busy cursor
      */
     protected Cursor getBusyCursor() {
         return busyCursor;
     }

     /**
      * Sets the busy cursor.
      * @param busyCursor the busy cursor
      */
     protected void setBusyCursor(Cursor busyCursor) {
         this.busyCursor = busyCursor;
     }

     /**
      * Gets the hand cursor.
      * @return Returns a hand cursor
      */
     protected Cursor getHandCursor() {
         return handCursor;
     }

     /**
      * Sets the hand cursor.
      * @param handCursor The hand cursor to set
      */
     protected void setHandCursor(Cursor handCursor) {
         this.handCursor = handCursor;
     }

     /**
      * Gets the about item.
      * @return the about item
      */
     protected AboutItem getItem() {
         return item;
     }

     /**
      * Sets the about item.
      * @param item about item
      */
     protected void setItem(AboutItem item) {
         this.item = item;
     }

     /**
      * Find the range of the current selection.
      */
     protected StyleRange getCurrentRange(StyledText text) {
         StyleRange[] ranges = text.getStyleRanges();
         int currentSelectionEnd = text.getSelection().y;
         int currentSelectionStart = text.getSelection().x;

         for (int i = 0; i < ranges.length; i++) {
             if ((currentSelectionStart >= ranges[i].start)
                     && (currentSelectionEnd <= (ranges[i].start + ranges[i].length))) {
                 return ranges[i];
             }
         }
         return null;
     }

     /**
      * Find the next range after the current
      * selection.
      */
     protected StyleRange findNextRange(StyledText text) {
         StyleRange[] ranges = text.getStyleRanges();
         int currentSelectionEnd = text.getSelection().y;

         for (int i = 0; i < ranges.length; i++) {
             if (ranges[i].start >= currentSelectionEnd) {
                 return ranges[i];
             }
         }
         return null;
     }

     /**
      * Find the previous range before the current selection.
      */
     protected StyleRange findPreviousRange(StyledText text) {
         StyleRange[] ranges = text.getStyleRanges();
         int currentSelectionStart = text.getSelection().x;

         for (int i = ranges.length - 1; i > -1; i--) {
             if ((ranges[i].start + ranges[i].length - 1) < currentSelectionStart) {
                 return ranges[i];
             }
         }
         return null;
     }

     /**
      * display an error message
      */
     private void openWebBrowserError(final String href, final Throwable t) {
         String title = WorkbenchMessages.ProductInfoDialog_errorTitle;
         String msg = NLS.bind(
                 WorkbenchMessages.ProductInfoDialog_unableToOpenWebBrowser,
                 href);
         IStatus status = WorkbenchPlugin.getStatus(t);
         StatusUtil.handleStatus(status, title + ": " + msg, StatusManager.SHOW, //$NON-NLS-1$
 getShell());
     }

     /**
      * Open a link
      */
     protected void openLink(String href) {
         // format the href for an html file (file:///<filename.html>
 // required for Mac only.
 if (href.startsWith("file:")) { //$NON-NLS-1$
 href = href.substring(5);
             while (href.startsWith("/")) { //$NON-NLS-1$
 href = href.substring(1);
             }
             href = "file:///" + href; //$NON-NLS-1$
 }
         IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
         try {
             IWebBrowser browser = support.getExternalBrowser();
             browser.openURL(new URL (urlEncodeForSpaces(href.toCharArray())));
         }
         catch (MalformedURLException e) {
             openWebBrowserError(href, e);
         }
         catch (PartInitException e) {
             openWebBrowserError(href, e);
         }
     }

     /**
      * This method encodes the url, removes the spaces from the url and replaces
      * the same with <code>"%20"</code>. This method is required to fix Bug
      * 77840.
      *
      * @since 3.0.2
      */
     private String urlEncodeForSpaces(char[] input) {
        StringBuffer retu = new StringBuffer (input.length);
        for (int i = 0; i < input.length; i++) {
            if (input[i] == ' ') {
             retu.append("%20"); //$NON-NLS-1$
 } else {
             retu.append(input[i]);
         }
        }
        return retu.toString();
     }

     /**
      * Open a browser with the argument title on the argument url. If the url refers to a
      * resource within a bundle, then a temp copy of the file will be extracted and opened.
      * @see <code>Platform.asLocalUrl</code>
      * @param url The target url to be displayed, null will be safely ignored
      * @return true if the url was successfully displayed and false otherwise
      */
     protected boolean openBrowser(URL url) {
         if (url != null) {
             try {
                 url = Platform.asLocalURL(url);
             } catch (IOException e) {
                 return false;
             }
         }
         if (url == null) {
             return false;
         }
         openLink(url.toString());
         return true;
     }

     /**
      * Sets the styled text's bold ranges
      */
     protected void setBoldRanges(StyledText styledText, int[][] boldRanges) {
         for (int i = 0; i < boldRanges.length; i++) {
             StyleRange r = new StyleRange(boldRanges[i][0], boldRanges[i][1],
                     null, null, SWT.BOLD);
             styledText.setStyleRange(r);
         }
     }

     /**
      * Sets the styled text's link (blue) ranges
      */
     protected void setLinkRanges(StyledText styledText, int[][] linkRanges) {
         Color fg = JFaceColors.getHyperlinkText(styledText.getShell()
                 .getDisplay());
         for (int i = 0; i < linkRanges.length; i++) {
             StyleRange r = new StyleRange(linkRanges[i][0], linkRanges[i][1],
                     fg, null);
             styledText.setStyleRange(r);
         }
     }

     /**
      * Scan the contents of the about text
      */
     protected AboutItem scan(String s) {
         ArrayList linkRanges = new ArrayList ();
         ArrayList links = new ArrayList ();
         
         // slightly modified version of jface url detection
 // see org.eclipse.jface.text.hyperlink.URLHyperlinkDetector

         int urlSeparatorOffset= s.indexOf("://"); //$NON-NLS-1$
 while(urlSeparatorOffset >= 0) {
     
             boolean startDoubleQuote= false;
     
             // URL protocol (left to "://")
 int urlOffset= urlSeparatorOffset;
             char ch;
             do {
                 urlOffset--;
                 ch= ' ';
                 if (urlOffset > -1)
                     ch= s.charAt(urlOffset);
                 startDoubleQuote= ch == '"';
             } while (Character.isUnicodeIdentifierStart(ch));
             urlOffset++;
             
     
             // Right to "://"
 StringTokenizer tokenizer= new StringTokenizer (s.substring(urlSeparatorOffset + 3), " \t\n\r\f<>", false); //$NON-NLS-1$
 if (!tokenizer.hasMoreTokens())
                 return null;
     
             int urlLength= tokenizer.nextToken().length() + 3 + urlSeparatorOffset - urlOffset;
     
             if (startDoubleQuote) {
                 int endOffset= -1;
                 int nextDoubleQuote= s.indexOf('"', urlOffset);
                 int nextWhitespace= s.indexOf(' ', urlOffset);
                 if (nextDoubleQuote != -1 && nextWhitespace != -1)
                     endOffset= Math.min(nextDoubleQuote, nextWhitespace);
                 else if (nextDoubleQuote != -1)
                     endOffset= nextDoubleQuote;
                 else if (nextWhitespace != -1)
                     endOffset= nextWhitespace;
                 if (endOffset != -1)
                     urlLength= endOffset - urlOffset;
             }
             
             linkRanges.add(new int[] { urlOffset, urlLength });
             links.add(s.substring(urlOffset, urlOffset+urlLength));
             
             urlSeparatorOffset= s.indexOf("://", urlOffset+urlLength+1); //$NON-NLS-1$
 }
         return new AboutItem(s, (int[][]) linkRanges.toArray(new int[linkRanges
                 .size()][2]), (String []) links
                 .toArray(new String [links.size()]));
     }

 }

